Skip to content

fix(proxyabl): close trailing-slash scope-check bypass in middleware#36

Open
davidcrowe wants to merge 1 commit into
mainfrom
fix/proxyabl-middleware-failopen
Open

fix(proxyabl): close trailing-slash scope-check bypass in middleware#36
davidcrowe wants to merge 1 commit into
mainfrom
fix/proxyabl-middleware-failopen

Conversation

@davidcrowe

Copy link
Copy Markdown
Collaborator

Problem

createProxyablMiddleware derives the tool name to scope-check from the request path:

const tool = req.path.split("/").pop();   // "" when path ends in "/"
if (tool) {
  ensureToolScopesForRequest(config, tool, identity.scopes);
}

A trailing slash (or a double slash) makes the final segment an empty string, the if (tool) guard is false, and the scope check is skipped entirely. A caller without deploy:write who requests /proxy/deploy is correctly rejected (403) — but the same caller requesting /proxy/deploy/ sails through. That's a fail-open in a deny-oriented layer, reachable by appending one character.

Fix

Derive the tool from the last non-empty path segment, making enforcement invariant to trailing and duplicate slashes:

const tool = req.path.split("/").filter(Boolean).pop();

Verification

New regression test (packages/proxyabl/__tests__/middleware.scope.test.ts) drives the middleware through a real gateway context:

  • holds required scope → next()
  • lacks scope, /proxy/deploy → 403 ✓
  • lacks scope, /proxy/deploy/ (trailing slash) → 403
  • lacks scope, /proxy//deploy// (double slash) → 403

The two slash cases fail against the current code (confirmed by reverting the one-line fix and re-running) and pass with it. Full suite: 18 files / 163 tests green.

Out of scope (flagging for a policy decision)

This does not touch the separate question of whether a tool with no configured scopes should be allowed by default — currently assertToolScopes returns early when a tool declares no required scopes, so unknown/unconfigured tools pass. Whether proxyabl should deny unknown tools outright is a deny-by-default policy decision that belongs with validatabl's model, not this bugfix. Happy to open a separate issue if we want to change it.

createProxyablMiddleware derived the tool name with req.path.split('/').pop(),
which returns an empty string when the path has a trailing slash (or a double
slash). The subsequent `if (tool)` guard then skipped the scope check entirely,
so a request to `/proxy/deploy/` bypassed the scopes configured for `deploy` —
a fail-open in a deny-oriented layer. Derive the tool from the last non-empty
segment so enforcement is invariant to trailing and duplicate slashes. Adds a
regression test that fails against the old code (verified) and passes now.

Note: this does not change the separate policy question of whether tools with
NO configured scopes should be allowed by default (current behavior) — that is
validatabl's deny-by-default domain and is left unchanged here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant